package logger

import (
	"context"
	"fmt"
	"time"

	"go.uber.org/zap"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/utils"
)

type ormLogger struct {
	zap *zap.SugaredLogger
	logger.Config
	infoStr, warnStr, errStr            string
	traceStr, traceErrStr, traceWarnStr string
}

//New *ormLogger
func NewOrmLogger(zap *zap.SugaredLogger, config logger.Config) *ormLogger {
	var (
		infoStr      = "%s\n[info] "
		warnStr      = "%s\n[warn] "
		errStr       = "%s\n[error] "
		traceStr     = "%s\n[%.3fms] [rows:%v] %s"
		traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s"
		traceErrStr  = "%s %s\n[%.3fms] [rows:%v] %s"
	)
	return &ormLogger{
		zap:          zap,
		Config:       config,
		infoStr:      infoStr,
		warnStr:      warnStr,
		errStr:       errStr,
		traceStr:     traceStr,
		traceWarnStr: traceWarnStr,
		traceErrStr:  traceErrStr,
	}
}

func (o *ormLogger) LogMode(level logger.LogLevel) logger.Interface {
	newLogger := *o
	newLogger.LogLevel = level
	return &newLogger
}

func (o *ormLogger) Info(ctx context.Context, msg string, data ...interface{}) {
	if o.LogLevel >= logger.Info {
		o.zap.Infof(o.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

func (o *ormLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
	if o.LogLevel >= logger.Warn {
		o.zap.Warnf(o.warnStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

func (o *ormLogger) Error(ctx context.Context, msg string, data ...interface{}) {
	if o.LogLevel >= logger.Error {
		o.zap.Errorf(o.errStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

func (o *ormLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	if o.LogLevel > logger.Silent {
		elapsed := time.Since(begin)
		switch {
		case err != nil && o.LogLevel >= logger.Error:
			sql, rows := fc()
			if rows == -1 {
				o.zap.Errorf(o.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
			} else {
				o.zap.Errorf(o.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql)
			}
		case elapsed > o.SlowThreshold && o.SlowThreshold != 0 && o.LogLevel >= logger.Warn:
			sql, rows := fc()
			slowLog := fmt.Sprintf("SLOW SQL >= %v", o.SlowThreshold)
			if rows == -1 {
				o.zap.Warnf(o.traceWarnStr, o.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
			} else {
				o.zap.Warnf(o.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
			}
		case o.LogLevel == logger.Info:
			sql, rows := fc()
			if rows == -1 {
				o.zap.Infof(o.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql)
			} else {
				o.zap.Infof(o.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql)
			}
		}
	}
}
